﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using TFS.Kanban.Commons.Extensions;

namespace TFS.Kanban.VS2010.Queries
{
    public abstract class WIQLQuery<T>
    {
        private WorkItemStore _workItemStore;

        private IList<string> _fields = new List<string>();
        private IList<FilterGroup> _filterGroups =  new List<FilterGroup>();
        private FilterGroup _currentGroup;

        protected virtual string Fields { get { return "*"; } }
        protected abstract string Table { get; }

        internal WIQLQuery(WorkItemStore workItemStore)
        {
            _workItemStore = workItemStore;
        }

        public IEnumerable<T> Execute()
        {
            return _workItemStore.Query(GetQueryString()).Cast<T>();
        }

        public WIQLQuery<T> Where(Expression<Func<T, object>> property, WIQLOperator @operator, object value)
        {
            this.AddCondition(new FilterCondition(null, MapFieldName(property), @operator, value.ToString()));
            return this;
        }

        public WIQLQuery<T> And(Expression<Func<T, object>> property, WIQLOperator @operator, object value)
        {
            this.AddCondition(new FilterCondition(LogicalOperator.And, MapFieldName(property), @operator, value.ToString()));
            return this;
        }

        public WIQLQuery<T> Or(Expression<Func<T, object>> property, WIQLOperator @operator, object value)
        {
            this.AddCondition(new FilterCondition(LogicalOperator.Or, MapFieldName(property), @operator, value.ToString()));
            return this;
        }

        public WIQLQuery<T> NewConditionGroup()
        {
            _currentGroup = new FilterGroup();
            _filterGroups.Add(_currentGroup);
            return this;
        }

        public WIQLQuery<T> AddField(string field)
        {
            _fields.Add(field);
            return this;
        }

        protected internal WIQLQuery<T> AddCondition(FilterCondition condition)
        {
            if(_currentGroup == null) NewConditionGroup();
            if (_currentGroup != null) _currentGroup.AddCondition(condition);
            return this;
        }

        private string MapFieldName(Expression<Func<T, object>> property)
        {
            return MapFieldName(property.GetPropertyName());
        }

        protected virtual string MapFieldName(string propertyName)
        {
            return MapFieldName(propertyName);
        }

        protected string GetPropertyName(Expression<Func<T, object>> propertyExpression)
        {
            return propertyExpression.GetPropertyName();
        }

        internal string GetQueryString()
        {
            var result = new StringBuilder();

            result.Append(BuildFields());
            result.Append(" ");
            result.Append(BuildFrom());
            result.Append(" ");
            result.Append(BuildWhere());

            return result.ToString();
        }

        private string BuildFields()
        {
            var result = new StringBuilder();
            result.Append("SELECT ");

            if(_fields.Any())
            {
                result.Append(String.Join(", ", _fields));
            }
            else
            {
                result.Append("*");
            }

            return result.ToString();
        }

        private string BuildFrom()
        {
            return string.Format("FROM {0}", Table);
        }

        private string BuildWhere()
        {
            var result = new StringBuilder();

            if (_filterGroups.Any())
            {
                result.Append("WHERE ");

                foreach(var group in _filterGroups)
                {
                    result.Append(group.GetFilterGroup());
                    result.Append(" ");
                }
            }

            return result.ToString();
        }
    }
}
